/*
 * Copyright (c) 2006-2020, RT-Thread Development Team
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Change Logs:
 * Date           Author       Notes
 * 2020-05-21     chenhy       the first version
 */
#include "ymodem.h"
#include "stdcrc16.h"
#include "com.h"
#include "lfs_if.h"
#include "dev_api.h"
#include "proc_bar.h"
#include <string.h>
#include <stdlib.h>
#include <stdio.h>

/*YModem standard CMD*/
#define YMODEM_SOH    0x01
#define YMODEM_STX    0x02
#define YMODEM_EOT    0x04
#define YMODEM_ACK    0x06
#define YMODEM_NAK    0x15
#define YMODEM_CAN    0x18
#define YMODEM_C      0x43
#define YMODEM_CPMEOF 0x1A
#define YMODEM_FRAME_SIZE 133
#define YMODEM_START_HEAD "ymodem: "
#define YMODEM_START_CMD  "sending in batch mode etc."
#define YMODEM_NAME_SIZE  32
#define DEBUG(...)
int ymodem_service(uint8_t* pbuf, int size, unsigned int com_handler)
{
  static unsigned char pack_seq = 0;
  static char file_name[YMODEM_NAME_SIZE] = {0};
  static char temp_file_name[YMODEM_NAME_SIZE] = {0};
  static int file_size = 0;
  static int write_pos = 0;
  lfs_file_t *file;
  int write_size;
  com_dev_t *p_com = (com_dev_t *)com_handler;
  DEBUG("<ymodem_service>\n");
  if(memcmp(pbuf, YMODEM_START_HEAD, strlen(YMODEM_START_HEAD)) == 0) {
    pack_seq = 0;
    write_size = size - strlen(YMODEM_START_HEAD);
    memmove(&pbuf[0], &pbuf[strlen(YMODEM_START_HEAD)], write_size);
    p_com->write(pbuf, write_size);
    return EXIT_SUCCESS;
  }
  else if(memcmp(pbuf, YMODEM_START_CMD, strlen(YMODEM_START_CMD)) == 0) {
    pack_seq = 0;
    pbuf[0] = YMODEM_C;
    p_com->write(pbuf, 1);
    return EXIT_SUCCESS;
  }
  else if(pbuf[0]==YMODEM_SOH) {
    uint16_t cal_crc16;
    DEBUG("YMODEM_SOH\n");
    if(size != 133) {
      DEBUG("size != 133\n");
      return EXIT_FAILURE;
    }
    if(pbuf[1] != pack_seq) {
      DEBUG("pbuf[1] != pack_seq\n");
      memset(file_name, 0, YMODEM_NAME_SIZE);
      memset(temp_file_name, 0, YMODEM_NAME_SIZE);
      file_size = 0;
      write_pos = 0;
      pack_seq = 0;
      DEBUG("CAN\n");
      pbuf[0] = YMODEM_CAN;
      p_com->write(pbuf, 1);
      return EXIT_SUCCESS;
    }
    if((pbuf[1] ^ pbuf[2]) != 0xFF) {
      DEBUG("(pbuf[1] ^ pbuf[2]) != 0xFF\n");
      DEBUG("NAK\n");
      pbuf[0] = YMODEM_NAK;
      p_com->write(pbuf, 1);
      return EXIT_SUCCESS;
    }
    cal_crc16 = CRC16_XMODEM(&pbuf[3], 128);
    cal_crc16 = SWAP16(cal_crc16);
    if(memcmp(&cal_crc16, &pbuf[131], 2)) {
      DEBUG("cal_crc16 != CRC (0x%04X)\n", cal_crc16);
      DEBUG("NAK\n");
      pbuf[0] = YMODEM_NAK;
      p_com->write(pbuf, 1);
      return EXIT_SUCCESS;
    }
    else {
      if((file_size==0)&&(pack_seq==0)) {
        int free_size;
        if(strlen((char*)&pbuf[3]) == 0) {
          DEBUG("There is no file that it would be transmited.\n");
          memset(file_name, 0, YMODEM_NAME_SIZE);
          memset(temp_file_name, 0, YMODEM_NAME_SIZE);
          file_size = 0;
          write_pos = 0;
          pack_seq = 0;
          DEBUG("ACK\n");
          pbuf[0] = YMODEM_ACK;
          p_com->write(pbuf, 1);
          return EXIT_SUCCESS;
        }
        else if(strlen((char*)&pbuf[3]) > (YMODEM_NAME_SIZE-6)) {
          DEBUG("file name length is too large.\n");
          memset(file_name, 0, YMODEM_NAME_SIZE);
          memset(temp_file_name, 0, YMODEM_NAME_SIZE);
          file_size = 0;
          write_pos = 0;
          pack_seq = 0;
          DEBUG("CAN\n");
          pbuf[0] = YMODEM_CAN;
          p_com->write(pbuf, 1);
          return EXIT_SUCCESS;
        }
        sscanf((char*)&pbuf[3], "%s", file_name);
        sscanf((char*)&pbuf[3+strlen(file_name)+1], "%d", &file_size);
        memcpy(temp_file_name, file_name, strlen(file_name));
        memcpy(&temp_file_name[strlen(file_name)], ".temp", 5);
        printf("file name: %s\n", file_name);
        DEBUG("temp file name: %s\n", temp_file_name);
        printf("file size: %d Bytes\n", file_size);
        if(file_size == 0) {
          DEBUG("open file faild.\n");
          memset(file_name, 0, YMODEM_NAME_SIZE);
          memset(temp_file_name, 0, YMODEM_NAME_SIZE);
          file_size = 0;
          write_pos = 0;
          pack_seq = 0;
          DEBUG("CAN\n");
          pbuf[0] = YMODEM_CAN;
          p_com->write(pbuf, 1);
          return EXIT_SUCCESS;
        }
        file_remove(file_name);
        file_remove(temp_file_name);
        free_size = lfs_df();
        DEBUG("free size: %d\n", free_size);
        if(free_size > file_size) {
          file = file_open(temp_file_name, LFS_O_RDWR|LFS_O_CREAT);
          if(file == NULL) {
            DEBUG("open file faild.\n");
            memset(file_name, 0, YMODEM_NAME_SIZE);
            memset(temp_file_name, 0, YMODEM_NAME_SIZE);
            file_size = 0;
            write_pos = 0;
            pack_seq = 0;
            DEBUG("CAN\n");
            pbuf[0] = YMODEM_CAN;
            p_com->write(pbuf, 1);
            return EXIT_SUCCESS;
          }
          file_truncate(0, file);
          file_close(file);
          write_pos = 0;
          printf("ymodem: receving file ...\n");
          // bar_update(write_pos, file_size);
          DEBUG("ACK\n");
          pbuf[0] = YMODEM_ACK;
          p_com->write(pbuf, 1);
          HAL_Delay(500);
          DEBUG("C\n");
          pbuf[0] = YMODEM_C;
          p_com->write(pbuf, 1);
          pack_seq ++;
          return EXIT_SUCCESS;
        }
        else {
          DEBUG("free_size < file_size\n");
          memset(file_name, 0, YMODEM_NAME_SIZE);
          memset(temp_file_name, 0, YMODEM_NAME_SIZE);
          file_size = 0;
          write_pos = 0;
          pack_seq = 0;
          DEBUG("CAN\n");
          pbuf[0] = YMODEM_CAN;
          p_com->write(pbuf, 1);
          return EXIT_SUCCESS;
        }
      }
      else {
        file = file_open(temp_file_name, LFS_O_RDWR);
        if(file == NULL) {
          DEBUG("open file faild.\n");
          memset(file_name, 0, YMODEM_NAME_SIZE);
          memset(temp_file_name, 0, YMODEM_NAME_SIZE);
          file_size = 0;
          write_pos = 0;
          pack_seq = 0;
          DEBUG("CAN\n");
          pbuf[0] = YMODEM_CAN;
          p_com->write(pbuf, 1);
          return EXIT_SUCCESS;
        }
        file_seek(file, write_pos, LFS_SEEK_SET);
        file_write(&pbuf[3], 128, file);
        file_close(file);
        DEBUG("ACK\n");
        pbuf[0] = YMODEM_ACK;
        p_com->write(pbuf, 1);
        pack_seq ++;
        write_pos += 128;
        // bar_update(write_pos, file_size);
        return EXIT_SUCCESS;
      }
    }
  }
  else if(pbuf[0] == YMODEM_EOT) {
    DEBUG("YMODEM_EOT\n");
    if((size==1)&&(file_size!=0)) {
      uint8_t test[8];
      if(write_pos < file_size) {
        file_remove(temp_file_name);
        memset(file_name, 0, YMODEM_NAME_SIZE);
        memset(temp_file_name, 0, YMODEM_NAME_SIZE);
        file_size = 0;
        write_pos = 0;
        pack_seq = 0;
        DEBUG("CAN\n");
        pbuf[0] = YMODEM_CAN;
        p_com->write(pbuf, 1);
        return EXIT_SUCCESS;
      }
      file = file_open(temp_file_name, LFS_O_RDWR);
      if(file == NULL) {
        DEBUG("open file faild.\n");
        memset(file_name, 0, YMODEM_NAME_SIZE);
        memset(temp_file_name, 0, YMODEM_NAME_SIZE);
        file_size = 0;
        write_pos = 0;
        pack_seq = 0;
        DEBUG("CAN\n");
        pbuf[0] = YMODEM_CAN;
        p_com->write(pbuf, 1);
        return EXIT_SUCCESS;
      }
      file_truncate(file_size, file);
      file_seek(file, -8, LFS_SEEK_END);
      file_read(test, 8, file);
      file_close(file);
      printf("completed.\n");
      file_rename(temp_file_name, file_name);
      memset(file_name, 0, YMODEM_NAME_SIZE);
      memset(temp_file_name, 0, YMODEM_NAME_SIZE);
      file_size = 0;
      write_pos = 0;
      pack_seq = 0;
      DEBUG("ACK\n");
      pbuf[0] = YMODEM_ACK;
      p_com->write(pbuf, 1);
      HAL_Delay(500);
      DEBUG("C\n");
      pbuf[0] = YMODEM_C;
      p_com->write(pbuf, 1);
      return EXIT_SUCCESS;
    }
  }
  return EXIT_FAILURE;
}
